package io.github.wanggit.antrpc.commons.utils;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomUtils;

import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.ServerSocket;
import java.net.SocketException;
import java.util.Enumeration;
import java.util.TreeMap;

@Slf4j
public abstract class NetUtil {

    private NetUtil() {
        throw new IllegalStateException();
    }

    private static final TreeMap<String, String> ALL_NETWORK_INTERFACES = new TreeMap<>();
    private static final Object lock = new Object();
    private static boolean runed = false;

    /**
     * 随机获取一个本机可用的端口
     *
     * @return port
     */
    public static Integer randomVisiblePort() {
        int port = RandomUtils.nextInt(10000, 65530);
        while (!testLocalhostPort(port)) {
            port = RandomUtils.nextInt(10000, 65530);
        }
        return port;
    }

    /**
     * 获取本机IP
     *
     * @return ip
     */
    public static String getLocalIp() {
        try {
            InetAddress inetAddress = InetAddress.getLocalHost();
            String hostAddress = inetAddress.getHostAddress();
            allNetworkInterface();
            // 多网卡环境，可能getLocalHost取到不可用的IP，要再做一次处理
            if (!ALL_NETWORK_INTERFACES.containsKey(hostAddress)) {
                if (log.isWarnEnabled()) {
                    log.warn(
                            "There are many Network Interface in the system. \nIt seems that the "
                                    + hostAddress
                                    + " that is not available has been obtained. The list of available IP is "
                                    + JsonUtils.toJsonPrettyString(ALL_NETWORK_INTERFACES)
                                    + ". Random select the "
                                    + ALL_NETWORK_INTERFACES.firstKey()
                                    + "\nIf incorrect, [antrpc.exposed-ip=] can be used for configuration.");
                }
                return ALL_NETWORK_INTERFACES.firstKey();
            }
            return hostAddress;
        } catch (Exception e) {
            if (log.isErrorEnabled()) {
                log.error("Unable to obtain native IP address.", e);
            }
            throw new IllegalStateException();
        }
    }

    /**
     * 测试端口是否可用
     *
     * @param port port
     * @return true or false
     */
    public static boolean testLocalhostPort(int port) {
        ServerSocket socket = null;
        try {
            socket = new ServerSocket(port);
        } catch (Exception e) {
            return false;
        } finally {
            if (null != socket) {
                try {
                    socket.close();
                } catch (Exception e) {
                    if (log.isWarnEnabled()) {
                        log.warn("Close socket error.", e);
                    }
                }
            }
        }
        return true;
    }

    private static void allNetworkInterface() throws SocketException {
        if (!runed) {
            synchronized (lock) {
                if (!runed) {
                    runed = true;
                    Enumeration<NetworkInterface> networkInterfaces =
                            NetworkInterface.getNetworkInterfaces();
                    while (networkInterfaces.hasMoreElements()) {
                        NetworkInterface networkInterface = networkInterfaces.nextElement();
                        if (networkInterface.isUp() && !networkInterface.isVirtual()) {
                            String interfaceDisplayName = networkInterface.getDisplayName();
                            Enumeration<InetAddress> inetAddresses =
                                    networkInterface.getInetAddresses();
                            while (inetAddresses.hasMoreElements()) {
                                InetAddress inetAddress = inetAddresses.nextElement();
                                if (!inetAddress.isLoopbackAddress()
                                        && !inetAddress.isAnyLocalAddress()
                                        && !inetAddress.isLinkLocalAddress()) {
                                    ALL_NETWORK_INTERFACES.put(
                                            inetAddress.getHostAddress(), interfaceDisplayName);
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
